/*

=============================================================================

Copyright (c) 2010 Xiotex Studios Ltd.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The end-user documentation included with the redistribution, if any, must
include the following acknowledgment: "This product includes software
developed by Xiotex Studios Ltd (http://xiotexstudios.com)", in the same
place and form as other third-party acknowledgments. Alternately, this
acknowledgment may appear in the software itself, in the same form and
location as other such third-party acknowledgments.

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 

=============================================================================

File:		CSWF.cpp
Author:		Byron Atkinson-Jones

Update(s):	(Ola)Dotun Rominiyi (28/10/2010) - replaced built-in types for
											   basic types
											 - shuffled includes for better
											   compilation times
											 - switched initialisation to
											   implement the CSWFAbstractStream

=============================================================================

*/

#include "SWFBase.h"
#include "CSWF.h"
#include "CSWFStream.h"
#include "CSWFTagList.h"
#include "CSWFInterface.h"
#include "CSWFFileStream.h"

CSWF::CSWF()
{
	m_pStream = 0;
	m_pTagList = 0;
	m_fAccumulator = 0.0f;
	
}

CSWF::~CSWF()
{
	if(m_pStream)
	{
		delete m_pStream;
		m_pStream = 0;
	}
	
	if(m_pTagList)
	{
		delete m_pTagList;
		m_pTagList = 0;
	}
	
	std::vector<PCOMMAND>::iterator it = m_vCommands.begin();
	
	while(it != m_vCommands.end())
	{
		delete *it;
		++it;
	}
	
	Shutdown();
}

bool CSWF::Initialise(const std::string& strPath, CSWFInterface* pInterface)
{
	m_pInterface = pInterface;

	m_pStream = new CSWFStream();

	if(!m_pStream)
	{
		return false;
	}

	CSWFFileStream filestream(strPath.c_str());
	if((m_pStream->Initialise(&filestream)) == false)
	{
		return false;
	}

	bool ret = SetupFromStream(pInterface);

	delete m_pStream;

	return ret;
}
bool CSWF::Initialise(CSWFAbstractStream* pSourceStream, CSWFInterface* pInterface)
{
	m_pInterface = pInterface;

	m_pStream = new CSWFStream();

	if(!m_pStream)
	{
		return false;
	}

	if((m_pStream->Initialise(pSourceStream)) == false)
	{
		return false;
	}

	bool ret = SetupFromStream(pInterface);
	
	delete m_pStream;
	
	return ret;
}

void CSWF::Shutdown()
{
	if(m_pStream)
	{
		delete m_pStream;
		m_pStream = NULL;
	}
}

bool CSWF::SetupFromStream(CSWFInterface* pInterface)
{
	swf_stream pStream;

	pStream = m_pStream->GetStreamBeginning();

	// Is the stream a valid SWF file?
	if((m_pStream->GetBYTE(pStream + SWF_S) != 'S') &&
		(m_pStream->GetBYTE(pStream + SWF_W) != 'W') &&
		(m_pStream->GetBYTE(pStream + SWF_F) != 'F'))
	{
		// Seems it wasn't an swf...or it was compressed
		return false;
	}

	m_dwFlashVersion = m_pStream->GetBYTE(pStream + SWF_FLASH_VERSION);
	m_dwFrameRate = m_pStream->GetWORD(pStream + SWF_FRAME_RATE);
	m_dwFrameRate = m_dwFrameRate >> 8;
	m_dwFrameCount = m_pStream->GetWORD(pStream + SWF_FRAME_COUNT);
	m_dwSize = m_pStream->GetInt(pStream + SWF_FILE_SIZE);
	m_pStream->GetRect(&m_rMovieDimensions,pStream + SWF_FRAME_SIZE);

	// Convert the movie dimension sInt32o non-twip values
	m_rMovieDimensions.m_dwXMin /= SWF_TWIPS;
	m_rMovieDimensions.m_dwXMax /= SWF_TWIPS;
	m_rMovieDimensions.m_dwYMin /= SWF_TWIPS;
	m_rMovieDimensions.m_dwYMax /= SWF_TWIPS;

	m_fHalfWidth = static_cast<float32>(m_rMovieDimensions.m_dwXMax) * 0.5f;
	m_fHalfHeight = static_cast<float32>(m_rMovieDimensions.m_dwYMax) * 0.5f;

	m_pTagList = new CSWFTagList();

	// Generate a Tag list of the movie and extract the bits we want
	m_pTagList->GenerateTagList(m_pStream,pStream + SWF_START_OF_MOVIE);
	m_pTagList->ParsePlaceObject(m_pStream);
	m_pTagList->ParseRemoveObject(m_pStream);
	m_pTagList->ParseDefineShape(m_pStream);
	m_pTagList->ParseDefineSprite(m_pStream);

	// Go through the tag list and pull out the tags that are relevant to us and convert them sInt32o commands
	std::vector<SWF_TAG>::iterator it = m_pTagList->m_vTags.begin();

	while(it != m_pTagList->m_vTags.end())
	{
		SWF_TAG tag = *it;
		PCOMMAND pCommand = new COMMAND;

		switch(tag.m_dwType)
		{
		case SWF_PLACE_OBJECT_2:
			pCommand->m_eInstruction = PLACE_SPRITE;
			pCommand->m_iDepth = tag.m_iDepth;
			pCommand->m_fScaleX = tag.m_fScaleX;
			pCommand->m_fScaleY = tag.m_fScaleY;
			pCommand->m_fAngle = tag.m_fAngle;
			pCommand->m_fTX = tag.m_fTX - m_fHalfWidth;
			pCommand->m_fTY = tag.m_fTY - m_fHalfHeight;

			if(tag.m_dwID != 0xFFFFFFFF)
			{
				pCommand->m_fParentScaleX = tag.m_fParentScaleX;
				pCommand->m_fParentScaleY = tag.m_fParentScaleY;
				pCommand->m_fParentAngle = tag.m_fParentAngle;
				pCommand->m_fParentTX = tag.m_fParentTX;
				pCommand->m_fParentTY = tag.m_fParentTY;
				pCommand->m_fWidth = tag.m_fWidth;
				pCommand->m_fHeight = tag.m_fHeight;
				pCommand->m_bSetParent = true;
			}
			else 
			{
				pCommand->m_bSetParent = false;
			}


			if(tag.m_bHasColourTransform)
			{
				pCommand->m_fAlpha = (float32)(tag.m_ColourTransform.m_lAMult) / 256.0f;
			}
			else 
			{
				pCommand->m_fAlpha = 1.0f;
			}

			if(tag.m_strName.length() != 0)
			{
				// Should assert here on size of string to make sure of no buffer overruns
				strcpy(pCommand->m_cTextureName,tag.m_strName.c_str());
				pCommand->m_dwTexture = m_pInterface->GetTexture(pCommand->m_cTextureName);
			}
			else 
			{
				pCommand->m_cTextureName[0] = 0;
				pCommand->m_dwTexture = 0;
			}
			m_vCommands.push_back(pCommand);
			break;

		case SWF_REMOVE_OBJECT:
			pCommand->m_eInstruction = REMOVE_SPRITE;
			pCommand->m_iDepth = tag.m_iDepth;
			m_vCommands.push_back(pCommand);
			break;

		case SWF_SHOW_FRAME:
			pCommand->m_eInstruction = END_OF_FRAME;
			m_vCommands.push_back(pCommand);
			break;
		}

		++it;	
	}

	Reset();

	delete m_pTagList;

	return true;
}

void CSWF::Play(float32 fTimeDelta)
{
	m_fAccumulator += fTimeDelta;
	
	while(m_fAccumulator >= (1.0f / 30.0f))
	{
		if(m_it == m_vCommands.end())
		{
			Reset();
		}
		
		PlayFrame();
		
		m_fAccumulator -= (1.0f / 30.0f);
	}
	
}

void CSWF::PlayFrame()
{
	while(m_it != m_vCommands.end())
	{
		PCOMMAND pCommand = *m_it;
		
		switch(pCommand->m_eInstruction)
		{
			case PLACE_SPRITE:
				m_vDisplayList[pCommand->m_iDepth].m_pCommand = pCommand;
				
				if(pCommand->m_bSetParent)
				{
					m_vDisplayList[pCommand->m_iDepth].m_fScaleX = pCommand->m_fParentScaleX;
					m_vDisplayList[pCommand->m_iDepth].m_fScaleY = pCommand->m_fParentScaleY;
					m_vDisplayList[pCommand->m_iDepth].m_fAngle= pCommand->m_fParentAngle;
					m_vDisplayList[pCommand->m_iDepth].m_fTX = pCommand->m_fParentTX;
					m_vDisplayList[pCommand->m_iDepth].m_fTY = pCommand->m_fParentTY;
					m_vDisplayList[pCommand->m_iDepth].m_fWidth = pCommand->m_fWidth;
					m_vDisplayList[pCommand->m_iDepth].m_fHeight = pCommand->m_fHeight;
					
				}
				
				if(pCommand->m_dwTexture)
				{
					m_vDisplayList[pCommand->m_iDepth].m_dwTexture = pCommand->m_dwTexture;
					m_vDisplayList[pCommand->m_iDepth].m_fX = -(m_vDisplayList[pCommand->m_iDepth].m_fWidth * 0.5f);
					m_vDisplayList[pCommand->m_iDepth].m_fX1 = m_vDisplayList[pCommand->m_iDepth].m_fWidth * 0.5f;
					m_vDisplayList[pCommand->m_iDepth].m_fY = -(m_vDisplayList[pCommand->m_iDepth].m_fHeight * 0.5f);
					m_vDisplayList[pCommand->m_iDepth].m_fY1 = m_vDisplayList[pCommand->m_iDepth].m_fHeight * 0.5f;
				}
				
				if(pCommand->m_iDepth > m_iHighestLayer)
				{
					m_iHighestLayer = pCommand->m_iDepth;
				}
				break;
				
			case REMOVE_SPRITE:
				m_vDisplayList[pCommand->m_iDepth].m_pCommand = 0;
				m_vDisplayList[pCommand->m_iDepth].m_dwTexture = 0;
				break;
				
			case END_OF_FRAME:
				++m_it;
				return;
		}
		
		++m_it;
	}

}

void CSWF::Reset()
{
	m_it = m_vCommands.begin();
	m_iHighestLayer = 0;
	
	for(uInt32 dwI = 0; dwI < 30; ++dwI)
	{
		m_vDisplayList[dwI].m_pCommand = 0;
		m_vDisplayList[dwI].m_dwTexture = 0;
		m_vDisplayList[dwI].m_fScaleX = 0.0f;
		m_vDisplayList[dwI].m_fScaleY = 0.0f;
		m_vDisplayList[dwI].m_fTX = 0.0f;
		m_vDisplayList[dwI].m_fTY = 0.0f;
		m_vDisplayList[dwI].m_fAngle = 0.0f;
	}
}

void CSWF::Render(float32 fCentreX, float32 fCentreY, float32 fScale,float32 fAngle, bool bFlipX, bool bFlipY)
{
	float32 fX1;
	float32 fY1;
	float32 fX2;
	float32 fY2;
	float32 fX3;
	float32 fY3;
	float32 fX4;
	float32 fY4;
	float32 fScaleX;
	float32 fScaleY;
	float32 fTX;
	float32 fTY;
	
	sInt32 iDepth = m_iHighestLayer;
	
	while(iDepth >= 0)
	{
		if(m_vDisplayList[iDepth].m_pCommand)
		{
			fScaleX = m_vDisplayList[iDepth].m_pCommand->m_fScaleX * m_vDisplayList[iDepth].m_fScaleX;
			fScaleY = m_vDisplayList[iDepth].m_pCommand->m_fScaleY * m_vDisplayList[iDepth].m_fScaleY;
			fTX = m_vDisplayList[iDepth].m_pCommand->m_fTX;
			fTY = m_vDisplayList[iDepth].m_pCommand->m_fTY;
			
			Transform(m_vDisplayList[iDepth].m_fX,m_vDisplayList[iDepth].m_fY, fTX,fTY,fCentreX, fCentreY,fScaleX,fScaleY, m_vDisplayList[iDepth].m_pCommand->m_fAngle, bFlipX,bFlipY,fAngle,fScale,&fX1,&fY1);
			Transform(m_vDisplayList[iDepth].m_fX1,m_vDisplayList[iDepth].m_fY, fTX,fTY,fCentreX, fCentreY,fScaleX,fScaleY, m_vDisplayList[iDepth].m_pCommand->m_fAngle, bFlipX,bFlipY,fAngle,fScale,&fX2,&fY2);
			Transform(m_vDisplayList[iDepth].m_fX1,m_vDisplayList[iDepth].m_fY1, fTX,fTY,fCentreX, fCentreY,fScaleX,fScaleY, m_vDisplayList[iDepth].m_pCommand->m_fAngle, bFlipX,bFlipY,fAngle,fScale,&fX3,&fY3);
			Transform(m_vDisplayList[iDepth].m_fX,m_vDisplayList[iDepth].m_fY1, fTX,fTY,fCentreX, fCentreY,fScaleX,fScaleY, m_vDisplayList[iDepth].m_pCommand->m_fAngle, bFlipX,bFlipY,fAngle,fScale,&fX4,&fY4);
			
			m_pInterface->DrawQuad(m_vDisplayList[iDepth].m_pCommand->m_dwTexture,m_vDisplayList[iDepth].m_pCommand->m_fAlpha,fX1,fY1,fX2,fY2,fX3,fY3,fX4,fY4);
		}
		
		--iDepth;
	}
}

void CSWF::Transform(float32 fX, float32 fY, float32 fTX, float32 fTY, float32 fCentreX, float32 fCentreY, float32 fScaleX, float32 fScaleY, float32 fAngle, bool bFlipX, bool bFlipY, float32 fWorldAngle, float32 fWorldScale, float32* fXOut, float32* fYOut)
{
	float32 fCos = swfCosf(fAngle);
	float32 fSin = swfSinf(fAngle);
	float32 fCos1 = swfCosf(fWorldAngle);
	float32 fSin1 = swfSinf(fWorldAngle);
	
	// Apply local rotation rotation
	float32 fX1 = fX * fCos - fY * fSin;
	float32 fY1 = fX * fSin + fY * fCos;
	
	// Apply scale and translation
	fX1 *= fScaleX;
	fY1 *= fScaleY;
	fX1 += fTX;
	fY1 += fTY;
	
	// Apply world rotation
	float32 fX2 = fX1 * fCos1 - fY1 * fSin1;
	float32 fY2 = fX1 * fSin1 + fY1 * fCos1;
	
	fX1 = fX2;
	fY1 = fY2;
	
	// Apply the world scale
	fX1 *= fWorldScale;
	fY1 *= fWorldScale;
	
	if(bFlipX)
	{
		fX1 *= -1.0f;
	}
	
	if(bFlipY)
	{
		fY1 *= -1.0f;	
	}
	
	// Transform the final coordinates to the desired position of the sprite
	fX1 += fCentreX;
	fY1 += fCentreY;

	*fXOut = fX1;
	*fYOut = fY1;
}

